//
// ColourTable
// =============
// A Cinema 4D command plugin, providing a color palette
// By Andreas Block, January 23th 2012
// Mail: open@andreasblock.de
//
// For further information, see colourtable.h
//
//------------------------------------------------------------------------
// File: colourtable.cpp  - class implementation
//

#include "c4d_symbols.h"
#include "colourtable.h"

#ifdef CTDEBUG
void printContainer(const BaseContainer * const bc)
{
  LONG i = 0;

  if (!bc) {
    GePrint("bc NULL");
    return;
  }
  const GeData *gd = bc->GetIndexData(0);
  if (!gd) {
    GePrint("gd NULL");
  }
  while (gd) {
    String sText = "Idx " + LongToString(i) + " ID: " + LongToString(bc->GetIndexId(i)) +
            "(" + PtrToString((void*)bc->GetIndexId(i)) + ") Type: " + LongToString(gd->GetType());
    if (gd->GetType() == 15) {
      sText += "= " + LongToString(bc->GetLong(bc->GetIndexId(i)));
    }
    GePrint(sText);
    i++;
    gd = bc->GetIndexData(i);
  }
  return;
}
#else
#define printContainer(bc)
#endif

BaseContainer *ColourTablePrefsGet(void)  // may return NULL
{
  BaseContainer *pBcPrefs = GetWorldContainerInstance()->GetContainerInstance(ID_COLOURTABLE_PREFS);
  if (!pBcPrefs) {
    GetWorldContainerInstance()->SetContainer(ID_COLOURTABLE_PREFS, BaseContainer());
    pBcPrefs = GetWorldContainerInstance()->GetContainerInstance(ID_COLOURTABLE_PREFS); // pBcPrefs may be left NULL, here
  }
  return pBcPrefs;
}


// CLASS ColourTableSubDialog
ColourTableSubDialog::ColourTableSubDialog(void)
{
  g_idxSelected = -1;
  g_iFlagsDisplay = DLG_FLAGS_DISPLAY_NONE;
  g_iFlagsDisplayMask = DLG_FLAGS_DISPLAY_NAME | DLG_FLAGS_DISPLAY_VALUE;
  g_iNumColumns = COLORS_PER_ROW_NOTEXT;
  g_iNumColumnsText = COLORS_PER_ROW_TEXT;
  g_iDlg = 0;
  g_pArrColors = NULL;
  g_pArrCodes = NULL;
  g_iNumColors = 0;
  g_sCodePrefix = "";
}

void ColourTableSubDialog::Init(void)
{
  g_pBcPrefs = ColourTablePrefsGet();
  if (g_pBcPrefs) {
    g_iFlagsDisplay = g_pBcPrefs->GetLong(g_iDlg + BC_OFFS_L_FLAGS_DISPLAY, DLG_FLAGS_DISPLAY_NONE);
    g_iNumColumns = g_pBcPrefs->GetLong(g_iDlg + BC_OFFS_L_NUM_COLUMNS_NOTEXT, COLORS_PER_ROW_NOTEXT);
    g_iNumColumnsText = g_pBcPrefs->GetLong(g_iDlg + BC_OFFS_L_NUM_COLUMNS_TEXT, COLORS_PER_ROW_TEXT);
  }
  return;
}

void ColourTableSubDialog::ColorLabelSet(LONG idxColor)
{
  String  sName;
  Vector  vColor;
  Real    fBrightness;

  GetColorField(GadgetPtr(DLG_ID_COLOR_BASE(g_iDlg) + idxColor), vColor, fBrightness);
  if (g_iFlagsDisplay & DLG_FLAGS_DISPLAY_VALUE) {
    sName = "[" + RealToString(vColor.x * RCO 255.0, 3, 0) + ", " +
                  RealToString(vColor.y * RCO 255.0, 3, 0) + ", " +
                  RealToString(vColor.z * RCO 255.0, 3, 0) + "]";
  }
  if ( (g_iFlagsDisplay & DLG_FLAGS_DISPLAY_CODE) &&
        (NULL != g_pArrCodes) ) {
    if (sName.Content()) {
      sName += "  " + g_sCodePrefix + LongToString(ColorGetCode(idxColor));
    } else {
      sName = g_sCodePrefix + LongToString(ColorGetCode(idxColor));
    }
  }
  if (g_iFlagsDisplay & DLG_FLAGS_DISPLAY_NAME) {
    if (sName.Content()) {
      sName += "  " + ColorGetName(idxColor);
    } else {
      sName = ColorGetName(idxColor);
    }
  }
  if (sName.Content()) {
    SetString(DLG_ID_LABEL_BASE(g_iDlg) + idxColor, sName);
    if (g_idxSelected == idxColor) {
      SetDefaultColor(GadgetPtr(DLG_ID_LABEL_BASE(g_iDlg) + idxColor), COLOR_BG, g_colBGHighlight);
    }
  }
  return;
}

void ColourTableSubDialog::CreateDynamicColorTable(void)
{
  const LONG iNumCols = (g_iFlagsDisplay ? g_iNumColumnsText : g_iNumColumns);

  GroupBegin(0, BFH_SCALEFIT | BFV_SCALEFIT, iNumCols, 0, "", 0);
  {
    GroupBorderNoTitle(BORDER_THIN_IN);
    GroupBorderSpace(4, 4, 4, 4);
    if (g_iFlagsDisplay) {
      GroupSpace(2, 2);
    } else {
      GroupSpace(0, 0);
    }
    for (LONG idxColor = 0; idxColor < ColorGetCount(); idxColor++) {
      const Vector  vColor = ColorGetVector(idxColor);

      AddColorField(DLG_ID_COLOR_BASE(g_iDlg) + idxColor, BFH_SCALEFIT | BFV_TOP, SizePix(16), SizePix(16));
      SetColorField(GadgetPtr(DLG_ID_COLOR_BASE(g_iDlg) + idxColor), vColor, RCO 1.0, RCO 1.0, 0);
      if (g_iFlagsDisplay) {
        AddStaticText(DLG_ID_LABEL_BASE(g_iDlg) + idxColor, BFH_LEFT | BFV_FIT, 0, 0, "", 0);
      }
      ColorLabelSet(idxColor);
    }
  }
  GroupEnd();
  return;
}
  
Bool ColourTableSubDialog::CreateLayout(void)
{
  LONG iR, iG, iB;

  GetColorRGB(COLOR_BGFOCUS, iR, iG, iB);  // TODO: strange behaviour in constructor AND Init()?
  g_colBGHighlight = Vector((Real)iR / RCO 255.0, (Real)iG / RCO 255.0, (Real)iB / RCO 255.0);
  GetColorRGB(COLOR_BG, iR, iG, iB);
  g_colBGDefault = Vector((Real)iR / RCO 255.0, (Real)iG / RCO 255.0, (Real)iB / RCO 255.0);
  return LoadDialogResource(g_iDlg, NULL, 0);
}

void ColourTableSubDialog::ReLayout(void)
{
	LayoutFlushGroup(DLG_ID_SCROLLGROUP_CONTENT(g_iDlg));
	CreateDynamicColorTable();
	LayoutChanged(DLG_ID_SCROLLGROUP_CONTENT(g_iDlg));
  return;
}

Bool ColourTableSubDialog::InitValues(void)
{
  ReLayout(); // Fills color table, resets colors of static tables
  if (-1 != g_idxSelected) {
    SetColorField(GadgetPtr(DLG_ID_COLOR_FOUND(g_iDlg)), ColorGetVector(g_idxSelected), RCO 1.0, RCO 1.0, 0);
    if (g_pArrCodes) { // for those who have codes, show codes (workaround for pantones having no names, yet (TODO))
      SetString(GadgetPtr(DLG_ID_EDIT_SEARCH(g_iDlg)), LongToString(ColorGetCode(g_idxSelected)));
    } else {
      SetString(GadgetPtr(DLG_ID_EDIT_SEARCH(g_iDlg)), ColorGetName(g_idxSelected));
    }
  }
	return TRUE;
}

void ColourTableSubDialog::ScrollGroupMove(const LONG idxColor)
{
  Bool iSuccess;
  const LONG iColorsPerRow = (g_iFlagsDisplay ? 2 : g_iNumColumns);
  const LONG iNumRows = DIV_CEIL(ColorGetCount(), iColorsPerRow);
  LONG iX, iY, iW, iH;

  iSuccess = GetItemDim(GadgetPtr(DLG_ID_SCROLLGROUP_CONTENT(g_iDlg)), &iX, &iY, &iW, &iH);
  if (!iSuccess) {
    return;
  }
  const Real fPixPerRow = (Real)iH / (Real)iNumRows;
  const LONG iCurrentRow = DIV_CEIL(idxColor, iColorsPerRow);
  LONG iX1, iX2, iY1, iY2;

  iSuccess = GetVisibleArea(DLG_ID_SCROLLGROUP(g_iDlg), &iX1, &iY1, &iX2, &iY2);
  if (!iSuccess) {
    return;
  }
  const LONG iHVisArea = iY2 - iY1;
  LONG iRowMin = iCurrentRow - (LONG)(((Real)iHVisArea / fPixPerRow) / RCO 2.0);
  LONG iRowMax = iCurrentRow + (LONG)(((Real)iHVisArea / fPixPerRow) / RCO 2.0);

  if (iNumRows < iRowMax) {
    iRowMin -= (iRowMax - iNumRows);
    iRowMax = iNumRows;
  }
  if (0 > iRowMin) {
    iRowMax += (0 - iRowMin);
    iRowMin = 0;
    if (iNumRows < iRowMax) {
      iRowMax = iNumRows;
    }
  }
  iY1 = (LONG)(fPixPerRow * iRowMin);
  iY2 = (LONG)(fPixPerRow * iRowMax);
  SetVisibleArea(DLG_ID_SCROLLGROUP(g_iDlg), iX1, iY1, iX2, iY2);
  return;
}

void ColourTableSubDialog::SelectionSet(LONG idxSelect, Bool iFlagUpdateSearch)
{
  if (-1 != g_idxSelected) {
    // Reset last selection to default color
    SetDefaultColor(GadgetPtr(DLG_ID_LABEL_BASE(g_iDlg) + g_idxSelected), COLOR_BG, g_colBGDefault);
  }
  g_idxSelected = idxSelect;
  if (-1 != g_idxSelected) {
    // Highlight idxSelect
    SetDefaultColor(GadgetPtr(DLG_ID_LABEL_BASE(g_iDlg) + g_idxSelected), COLOR_BG, g_colBGHighlight);
    // Update top color and search string
    SetColorField(GadgetPtr(DLG_ID_COLOR_FOUND(g_iDlg)), ColorGetVector(g_idxSelected), RCO 1.0, RCO 1.0, 0);
    if (iFlagUpdateSearch) {
      SetString(GadgetPtr(DLG_ID_EDIT_SEARCH(g_iDlg)), ColorGetName(g_idxSelected));
    }
  }
  return;
}

LONG ColourTableSubDialog::ColorSearchAndSet(void)
{
  String  sSearch;
  LONG    idxFound = -1;
  LONG    iError;

  GetString(GadgetPtr(DLG_ID_EDIT_SEARCH(g_iDlg)), sSearch);
  for (LONG idxColor = 0; idxColor < ColorGetCount(); idxColor++) {
    String sCmp = ColorGetName(idxColor);
    if ( (sCmp.Left(sSearch.GetLength()).ToLower() == sSearch.ToLower()) ||
          ( (NULL != g_pArrCodes) &&
            (sSearch.ToLong(&iError) == ColorGetCode(idxColor)) &&
            (!iError) ) ) {
      idxFound = idxColor;
      break;
    }
  }
  // Set cursor highlighting and move scrollgroup
  if (-1 == idxFound) {
    SetDefaultColor(DLG_ID_EDIT_SEARCH(g_iDlg), COLOR_TEXTFOCUS, Vector(1.0, 0.0, 0.0));
  } else {
    SetDefaultColor(DLG_ID_EDIT_SEARCH(g_iDlg), COLOR_TEXTFOCUS, Vector(0.0, 1.0, 0.0));
    ScrollGroupMove(idxFound);
  }
  SelectionSet(idxFound, FALSE);
  return idxFound;
}

LONG ColourTableSubDialog::ColorGetCount(void)
{
  return g_iNumColors;
}

Vector ColourTableSubDialog::ColorGetVector(const LONG idxColor)
{
  return g_pArrColors[idxColor];
}

String ColourTableSubDialog::ColorGetName(const LONG idxColor)
{
  return GeLoadString(DLG_ID_STRING_BASE(g_iDlg) + idxColor);
}

LONG ColourTableSubDialog::ColorGetCode(const LONG idxColor)
{
  return g_pArrCodes[idxColor];
}

Bool ColourTableSubDialog::Command(LONG id, const BaseContainer &msg)
{
  if (DLG_ID_EDIT_SEARCH(g_iDlg) == id) {
    ColorSearchAndSet();
    return TRUE;
	}
  if ( (id >= (DLG_ID_COLOR_BASE(g_iDlg))) &&
       (id < (DLG_ID_COLOR_BASE(g_iDlg) + (ColorGetCount() << 1))) ) {
    ColorLabelSet(id - DLG_ID_COLOR_BASE(g_iDlg));
    SelectionSet(id - DLG_ID_COLOR_BASE(g_iDlg), TRUE);
    return TRUE;
  }
  return TRUE;
}

Bool ColourTableSubDialog::FlagsDisplayToggle(LONG iFlag)
{
  g_iFlagsDisplay ^= iFlag;
  if (g_pBcPrefs) {
    g_pBcPrefs->SetLong(g_iDlg + BC_OFFS_L_FLAGS_DISPLAY, g_iFlagsDisplay);
  }
  ReLayout();
	return (g_iFlagsDisplay & iFlag) ? TRUE : FALSE;
}

void ColourTableSubDialog::FlagsDisplayResetAll(void)
{
  g_iFlagsDisplay = DLG_FLAGS_DISPLAY_NONE;
  if (g_pBcPrefs) {
    g_pBcPrefs->SetLong(g_iDlg + BC_OFFS_L_FLAGS_DISPLAY, g_iFlagsDisplay);
  }
  ReLayout();
	return;
}

LONG ColourTableSubDialog::FlagsDisplayGet(void)
{
	return g_iFlagsDisplay;
}

Bool ColourTableSubDialog::FlagsDisplayIsSet(LONG iFlag)
{
	return (g_iFlagsDisplay & iFlag) ? TRUE : FALSE;
}

LONG ColourTableSubDialog::FlagsDisplayMaskGet(void)
{
	return g_iFlagsDisplayMask;
}

void ColourTableSubDialog::ColumnsSet(LONG iFlagText, LONG iNumColumns)
{
  if (iFlagText) {
    g_iNumColumnsText = iNumColumns << 1;
  } else {
    g_iNumColumns = iNumColumns;
  }
  if (g_pBcPrefs) {
    g_pBcPrefs->SetLong(g_iDlg + BC_OFFS_L_NUM_COLUMNS_TEXT, g_iNumColumnsText);
    g_pBcPrefs->SetLong(g_iDlg + BC_OFFS_L_NUM_COLUMNS_NOTEXT, g_iNumColumns);
  }
  ReLayout();
	return;
}


// CLASS ColourTableSubDialogRAL
void ColourTableSubDialogRAL::Init(void)
{
  g_iDlg = DLG_RAL;
  g_pArrColors = arrRAL;
  g_pArrCodes = arrRALCodes;
  g_iNumColors = (sizeof(arrRAL)/sizeof(arrRAL[0]));
  g_sCodePrefix = "RAL";
  g_iFlagsDisplayMask = DLG_FLAGS_DISPLAY_NAME | DLG_FLAGS_DISPLAY_CODE | DLG_FLAGS_DISPLAY_VALUE;
  SUPER::Init(); // Call last
  return;
}


// CLASS ColourTableSubDialogPantone
void ColourTableSubDialogPantone::Init(void)
{
  g_iDlg = DLG_PANTONE;
  g_pArrColors = arrPantone;
  g_pArrCodes = arrPantoneCodes;
  g_iNumColors = (sizeof(arrPantone)/sizeof(arrPantone[0]));
  g_iFlagsDisplayMask = DLG_FLAGS_DISPLAY_CODE | DLG_FLAGS_DISPLAY_VALUE;
  SUPER::Init(); // Call last
  return;
}


// CLASS ColourTableSubDialogVGA
void ColourTableSubDialogVGA::Init(void)
{
  g_iDlg = DLG_VGA;
  g_pArrColors = arrVGA;
  g_iNumColors = (sizeof(arrVGA)/sizeof(arrVGA[0]));
  SUPER::Init(); // Call last
  return;
}


// CLASS ColourTableSubDialogX11
void ColourTableSubDialogX11::Init(void)
{
  g_iDlg = DLG_X11;
  g_pArrColors = arrX11;
  g_iNumColors = (sizeof(arrX11)/sizeof(arrX11[0]));
  SUPER::Init(); // Call last
  return;
}


// CLASS ColourTableSubDialogCSS3
void ColourTableSubDialogCSS3::Init(void)
{
  g_iDlg = DLG_CSS3;
  g_pArrColors = arrCSS3;
  g_iNumColors = (sizeof(arrCSS3)/sizeof(arrCSS3[0]));
  SUPER::Init(); // Call last
  return;
}


// CLASS ColourTableSubDialogGrey
void ColourTableSubDialogGrey::Init(void)
{
  g_iDlg = DLG_GREY;
  g_iNumColors = 11;
  g_fGreyPercent = RCO -1.0;
  SUPER::Init(); // Call last
}

Bool ColourTableSubDialogGrey::InitValues(void)
{
  if (g_pBcPrefs) {
    g_iNumColors = g_pBcPrefs->GetLong(g_iDlg + BC_OFFS_L_NUM_COLORS, g_iNumColors);
    g_fGreyPercent = g_pBcPrefs->GetReal(g_iDlg + BC_OFFS_R_GREY, RCO -1.0);
  }
  if (RCO -1.0 == g_fGreyPercent) {
    g_fGreyPercent = RCO 0.5;
    SetPercent(GadgetPtr(DLG_ID_SLIDER_PERCENT(g_iDlg)), g_fGreyPercent, RCO 0.0, RCO 100.0, RCO 0.2);
    SetString(GadgetPtr(DLG_ID_COLOR_FOUND_NAME(g_iDlg)), RealToString(g_fGreyPercent * RCO 100.0, 0, 1) + "% Grey"); // TODO localize
    SetColorField(GadgetPtr(DLG_ID_COLOR_FOUND(g_iDlg)), Vector(g_fGreyPercent), RCO 1.0, RCO 1.0, 0);
  } else {
    SetPercent(GadgetPtr(DLG_ID_SLIDER_PERCENT(g_iDlg)), g_fGreyPercent, RCO 0.0, RCO 100.0, RCO 0.2);
    SetString(GadgetPtr(DLG_ID_COLOR_FOUND_NAME(g_iDlg)), RealToString(g_fGreyPercent * RCO 100.0, 0, 1) + "% Grey");
    SetColorField(GadgetPtr(DLG_ID_COLOR_FOUND(g_iDlg)), Vector(g_fGreyPercent), RCO 1.0, RCO 1.0, 0);
  }
  SetLong(GadgetPtr(DLG_ID_SLIDER_NUM_COLORS(g_iDlg)), g_iNumColors, 1, 256);
  return SUPER::InitValues();
}

Real ColourTableSubDialogGrey::GreyCalc(const LONG idxColor)
{
  Real fGrey;

  if (0 == idxColor) {
    fGrey = RCO 0.0;
  } else if (1 == g_iNumColors) {
    fGrey = RCO 1.0;
  } else {
    fGrey = (Real)idxColor / (Real)(g_iNumColors - 1);
  }
  return fGrey;
}

Vector ColourTableSubDialogGrey::ColorGetVector(const LONG idxColor)
{
  return Vector(GreyCalc(idxColor));
}

String ColourTableSubDialogGrey::ColorGetName(const LONG idxColor)
{
  return RealToString(GreyCalc(idxColor) * RCO 100.0, 0, 1) + "%";
}

LONG ColourTableSubDialogGrey::ColorGetCode(const LONG idxColor)
{
  return (LONG)(GreyCalc(idxColor) * RCO 25500.0);
}

Bool ColourTableSubDialogGrey::Command(LONG id, const BaseContainer &msg)
{
  if (DLG_ID_SLIDER_PERCENT(g_iDlg) == id) {
    GetReal(GadgetPtr(DLG_ID_SLIDER_PERCENT(g_iDlg)), g_fGreyPercent);
    SetString(GadgetPtr(DLG_ID_COLOR_FOUND_NAME(g_iDlg)),
              RealToString(g_fGreyPercent * RCO 100.0, 0, 1) + "% " + GeLoadString(COLOURTABLE_SUBDIALOG_NAME_GREY));
    SetColorField(GadgetPtr(DLG_ID_COLOR_FOUND(g_iDlg)), Vector(g_fGreyPercent), RCO 1.0, RCO 1.0, 0);
    if (g_pBcPrefs) {
      g_pBcPrefs->SetReal(g_iDlg + BC_OFFS_R_GREY, g_fGreyPercent);
    }
    return TRUE;
  } else if (DLG_ID_SLIDER_NUM_COLORS(g_iDlg) == id) {
    GetLong(GadgetPtr(DLG_ID_SLIDER_NUM_COLORS(g_iDlg)), g_iNumColors);
    ReLayout();
    if (g_pBcPrefs) {
      g_pBcPrefs->SetLong(g_iDlg + BC_OFFS_L_NUM_COLORS, g_iNumColors);
    }
    return TRUE;
  }
  return SUPER::Command(id, msg);
}


// CLASS ColourTableSubDialogUserC4D
void ColourTableSubDialogUserC4D::Init(void)
{
  g_iDlg = DLG_USER_C4D;
  g_iFlagChangeDoc = FALSE;
  SUPER::Init(); // Call last
  return;
}

BaseContainer* ColourTableSubDialogUserC4D::ColorsGetContainer(void)
{
  return g_pBcPrefs;
}

void ColourTableSubDialogUserC4D::ColorAdd(void)
{
  BaseContainer *bcColors = ColorsGetContainer();
  if (NULL == bcColors) {
    return;
  }
  Vector         vColor;
  Real           fBrightness;
  String         sName;

  GetColorField(GadgetPtr(DLG_ID_COLOR_FOUND(g_iDlg)), vColor, fBrightness);
  GetString(GadgetPtr(DLG_ID_EDIT_SEARCH(g_iDlg)), sName);
  if ( sName.Content() &&
       (COLOURTABLE_USER_COLORS_MAX > ColorGetCount()) ) {
    if (-1 == g_idxSelected) {
      const LONG idxNew = ColorGetCount();

      bcColors->SetString(g_iDlg + BC_OFFS_STR_NAME_BASE + idxNew, sName);
      bcColors->SetVector(g_iDlg + BC_OFFS_V_COLOR_BASE + idxNew, vColor);
      bcColors->SetLong(g_iDlg + BC_OFFS_L_NUM_COLORS, idxNew + 1);
      g_idxSelected = idxNew;
    } else {
      bcColors->SetString(g_iDlg + BC_OFFS_STR_NAME_BASE + g_idxSelected, sName);
      bcColors->SetVector(g_iDlg + BC_OFFS_V_COLOR_BASE + g_idxSelected, vColor);
    }
    ReLayout();
    if (g_iFlagChangeDoc) {
      GetActiveDocument()->SetChanged(); // TODO rather add proper undo?
    }
  }
  return;
}

void ColourTableSubDialogUserC4D::ColorDelete(void)
{
  BaseContainer *bcColors = ColorsGetContainer();
  if (NULL == bcColors) {
    return;
  }

  if (-1 != g_idxSelected) {
    for (LONG idxColor = g_idxSelected; idxColor < (ColorGetCount() - 1); idxColor++) {
      bcColors->SetString(g_iDlg + BC_OFFS_STR_NAME_BASE + idxColor, ColorGetName(idxColor + 1));
      bcColors->SetVector(g_iDlg + BC_OFFS_V_COLOR_BASE + idxColor, ColorGetVector(idxColor + 1));
    }
    bcColors->SetLong(g_iDlg + BC_OFFS_L_NUM_COLORS, ColorGetCount() - 1);
    g_idxSelected = -1;
    ReLayout();
    if (g_iFlagChangeDoc) {
      GetActiveDocument()->SetChanged(); // TODO rather add proper undo?
    }
  }
  return;
}

Bool ColourTableSubDialogUserC4D::Command(LONG id, const BaseContainer &msg)
{
  if (DLG_ID_BUTTON_ADD(g_iDlg) == id) {
    ColorAdd();
    return TRUE;
	}
  if (DLG_ID_BUTTON_DEL(g_iDlg) == id) {
    ColorDelete();
    return TRUE;
	}
  if ( (id >= (DLG_ID_COLOR_BASE(g_iDlg))) &&
       (id < (DLG_ID_COLOR_BASE(g_iDlg) + (ColorGetCount() << 1))) ) {
    BaseContainer * const bcColors = ColorsGetContainer();

    if (bcColors) {
      const LONG idxColorChanged = id - DLG_ID_COLOR_BASE(g_iDlg);
      Vector     vColor;
      Real       fBrightness;

      GetColorField(GadgetPtr(id), vColor, fBrightness);
      // Store changed color and mark doc as changed
      bcColors->SetVector(g_iDlg + BC_OFFS_V_COLOR_BASE + idxColorChanged, vColor);
      if (g_iFlagChangeDoc) {
        GetActiveDocument()->SetChanged(); // TODO rather add proper undo?
      }
      // Don't return TRUE, parent needs to do some more work on this Command
    }
  }
  return SUPER::Command(id, msg);
}

LONG ColourTableSubDialogUserC4D::ColorGetCount(void)
{
  BaseContainer *bcColors = ColorsGetContainer();
  if (NULL == bcColors) {
    return 0;
  } 
  return bcColors->GetLong(g_iDlg + BC_OFFS_L_NUM_COLORS, 0);
}

Vector ColourTableSubDialogUserC4D::ColorGetVector(const LONG idxColor)
{
  BaseContainer *bcColors = ColorsGetContainer();
  if (NULL == bcColors) {
    return Vector(RCO 0.0);
  } 
  return bcColors->GetVector(g_iDlg + BC_OFFS_V_COLOR_BASE + idxColor, Vector(RCO 0.0));
}

String ColourTableSubDialogUserC4D::ColorGetName(const LONG idxColor)
{
  BaseContainer *bcColors = ColorsGetContainer();
  if (NULL == bcColors) {
    return "No container???";
  } 
  return bcColors->GetString(g_iDlg + BC_OFFS_STR_NAME_BASE + idxColor, "No name???");
}

LONG ColourTableSubDialogUserC4D::ColorGetCode(const LONG idxColor)
{
  return 0;
}


// CLASS ColourTableSubDialogUserDoc
BaseContainer* ColourTableSubDialogUserDoc::ColorsGetContainer(void)
{
  BaseDocument  *doc = GetActiveDocument();
  BaseContainer *bcDoc, *bcResult;
  
  bcDoc = doc->GetSettingsInstance(DOCUMENTSETTINGS_DOCUMENT);
  if (NULL == bcDoc) {
    return NULL;
  }
  bcResult = bcDoc->GetContainerInstance(ID_COLOURTABLE_DOC);
  if (NULL == bcResult) {
    bcDoc->SetContainer(ID_COLOURTABLE_DOC, BaseContainer());
    bcResult = bcDoc->GetContainerInstance(ID_COLOURTABLE_DOC);
  }
  return bcResult;
}

void ColourTableSubDialogUserDoc::Init(void)
{
  g_iDlg = DLG_USER_DOC;
  g_iFlagChangeDoc = TRUE;
  ColourTableSubDialog::Init(); // explicitly call ColourTableSubDialog::Init() and not SUPER::Init()
  return;
}

Bool ColourTableSubDialogUserDoc::CoreMessage(LONG id, const BaseContainer &msg)
{
	switch (id) {
  case EVMSG_CHANGE:
    ReLayout();
    break;
	}
  return SUPER::CoreMessage(id, msg);
}

void ColourTableSubDialogUserDoc::RemoveFromDoc(void)
{
  BaseDocument  *doc = GetActiveDocument();
  BaseContainer *bcDoc;
  
  bcDoc = doc->GetSettingsInstance(DOCUMENTSETTINGS_DOCUMENT);
  if (NULL == bcDoc) {
    return;
  }
  bcDoc->RemoveData(ID_COLOURTABLE_DOC);
  if (g_iFlagChangeDoc) {
    GetActiveDocument()->SetChanged(); // TODO rather add proper undo?
  }
  ReLayout();
  return;
}


// CLASS ColourTableAsyncDialog
ColourTableAsyncDialog::ColourTableAsyncDialog(void)
{
  g_pBcPrefs = ColourTablePrefsGet();
  IconOpen();
  g_iFlagsGroup = 0x00000001;
  g_iFlagWeightsSaved = FALSE;
  g_iNumColumns = 1;
  g_iNumColumnsActual = 1;
  g_iNumRows = 1;
  g_pArrSubdialogs[0] = &g_sdRAL;
  g_sdRAL.Init();
  g_pArrSubdialogs[1] = &g_sdPantone;
  g_sdPantone.Init();
  g_pArrSubdialogs[2] = &g_sdVGA;
  g_sdVGA.Init();
  g_pArrSubdialogs[3] = &g_sdX11;
  g_sdX11.Init();
  g_pArrSubdialogs[4] = &g_sdCSS3;
  g_sdCSS3.Init();
  g_pArrSubdialogs[5] = &g_sdGrey;
  g_sdGrey.Init();
  g_pArrSubdialogs[6] = &g_sdUserDoc;
  g_sdUserDoc.Init();
  g_pArrSubdialogs[7] = &g_sdUserC4D;
  g_sdUserC4D.Init();
  return;
}

ColourTableAsyncDialog::~ColourTableAsyncDialog(void)
{
  if (g_pBmIcon) {
    BaseBitmap::Free(g_pBmIcon);
    g_pBmIcon = NULL;
  }
  return;
}

void ColourTableAsyncDialog::IconOpen(void)
{
  const Filename         fn = GeGetPluginPath() + "res" + "colourtable.tif";
  AutoAlloc<BaseBitmap>  bmTemp;
  IMAGERESULT            res;

  g_pBmIcon = NULL;
  res = bmTemp->Init(fn);
  if (IMAGERESULT_OK != res) {
#ifdef CTDEBUG
    GePrint("Image open error"); // TODO localize?
#endif
    return;
  }
  g_pBmIcon = BaseBitmap::Alloc();
  if (!g_pBmIcon) {
#ifdef CTDEBUG
    GePrint("Bitmap alloc error"); // TODO localize?
#endif
    return;
  }
  res = g_pBmIcon->Init(18, 18); // tried to achieve same size as icon in menu
  // TODO size correct?, perhaps rather relative to some other gui size?
  if (IMAGERESULT_OK != res) {
    BaseBitmap::Free(g_pBmIcon);
    g_pBmIcon = NULL;
#ifdef CTDEBUG
    GePrint("Bitmap init error"); // TODO localize?
#endif
    return;
  }
  bmTemp->ScaleIt(g_pBmIcon, 256, TRUE, FALSE); // , FALSE, FALSE);
  //bmTemp->ScaleBicubic(g_bmIcon, 0, 0, 31, 31, 0, 17, 0, 17); // TODO quite sure, I've seen this one working...
  return;
}

Bool ColourTableAsyncDialog::CreateLayout(void)
{
	// first call the parent instance
	Bool iResult = SUPER::CreateLayout();

  if (g_pBcPrefs) {
    g_iFlagsGroup = g_pBcPrefs->GetLong(BC_L_FLAGS_GROUP, g_iFlagsGroup);
    g_iNumColumns = g_pBcPrefs->GetLong(BC_L_NUM_COLUMNS, g_iNumColumns);
    g_iNumColumnsActual = g_pBcPrefs->GetLong(BC_L_NUM_COLUMNS_ACTUAL, g_iNumColumnsActual);
    g_iNumRows = g_pBcPrefs->GetLong(BC_L_NUM_ROWS, g_iNumRows);
    g_pBcWeights = g_pBcPrefs->GetContainerInstance(BC_BC_WEIGHTS);
    if (!g_pBcWeights) {
      g_pBcPrefs->SetContainer(BC_BC_WEIGHTS, BaseContainer());
      g_pBcWeights = g_pBcPrefs->GetContainerInstance(BC_BC_WEIGHTS);
      // TODO may be NULL again

      g_pBcWeights->SetLong(GROUPWEIGHTS_PERCENT_W_CNT, g_iNumColumnsActual); // number of columns - has to be equal to the given layout
      for (LONG idxGroup = 0; idxGroup < COLOURTABLE_NUM_GROUPS; idxGroup++) { // preset enough weights for all groups
        g_pBcWeights->SetReal(GROUPWEIGHTS_PERCENT_W_VAL + idxGroup, 10.0);
      }
      // set the rows
      g_pBcWeights->SetLong(GROUPWEIGHTS_PERCENT_H_CNT, g_iNumRows); // number of rows - has to be equal to the given layout
      for (LONG idxGroup = 0; idxGroup < COLOURTABLE_NUM_GROUPS; idxGroup++) { // preset enough weights for all groups
        g_pBcWeights->SetReal(GROUPWEIGHTS_PERCENT_H_VAL + idxGroup, 10.0);
      }
    }
  }
  if (!LoadDialogResource(DLG_COLOURTABLE, NULL, 0)) {
    return FALSE;
  }
  g_pGadIcon = (BitmapButtonCustomGui*)FindCustomGui(COLOURTABLE_BITMAP_ICON, ID_COLOURTABLE);
  if (NULL == g_pGadIcon) {
    return FALSE;
  }
  if (g_pBmIcon) {
    g_pGadIcon->SetImage(g_pBmIcon, FALSE);
  }
  g_pGadQuickTab = (QuickTabCustomGui*)FindCustomGui(COLOURTABLE_QUICKTAB, ID_COLOURTABLE);
  if (NULL == g_pGadQuickTab) {
    return FALSE;
  }
  LONG iFlagsTemp = g_iFlagsGroup;
  for (LONG idxGroup = 0; idxGroup < COLOURTABLE_NUM_GROUPS; idxGroup++) {
    g_pGadQuickTab->AppendString(COLOURTABLE_SUBDIALOG_ID_BASE + idxGroup,
                                 GeLoadString(COLOURTABLE_SUBDIALOG_NAME_RAL + idxGroup),
                                 (iFlagsTemp & 0x00000001 ? TRUE : FALSE));
    iFlagsTemp >>= 1;
  }
  ReLayout(); // Enable selected groups
  MenuFlushAll();
 		MenuSubBegin(GeLoadString(COLOURTABLE_M_STR_VIEW));
      for (LONG idxGroup = 0; idxGroup < COLOURTABLE_NUM_GROUPS; idxGroup++) {
        MenuSubBegin(GeLoadString(COLOURTABLE_SUBDIALOG_NAME_RAL + idxGroup));
          for (LONG iFlagMask = 0x00000001; iFlagMask < DLG_FLAGS_DISPLAY_ALL; iFlagMask <<= 1) {
            if (g_pArrSubdialogs[idxGroup]->FlagsDisplayMaskGet() & iFlagMask) {
              LONG iMenuId = COLOURTABLE_M_VIEW_BASE | (idxGroup << 4) | iFlagMask;
              MenuAddString(iMenuId, GeLoadString(COLOURTABLE_M_VIEW_STR_BASE | iFlagMask)); //"Show names");
              MenuInitString(iMenuId, TRUE, g_pArrSubdialogs[idxGroup]->FlagsDisplayIsSet(iFlagMask));
            }
          }
   		  MenuSubEnd();
      }
	  MenuSubEnd();
    MenuSubBegin(GeLoadString(COLOURTABLE_M_STR_OPTIONS));
      MenuSubBegin(GeLoadString(COLOURTABLE_M_OPTIONS_STR_LAYOUT));
        for (LONG idxLayout = 1; idxLayout <= 8; idxLayout *= 2) { // parameter: num layout columns: 1, 2, 4, 8
          MenuAddString(COLOURTABLE_M_OPTIONS_SUBM_LAYOUT | idxLayout,
                        LongToString(idxLayout) + "x" + LongToString(8 / idxLayout));
        }
   	  MenuSubEnd();
   		MenuSubBegin(GeLoadString(COLOURTABLE_M_OPTIONS_STR_COLS_TEXT));
        for (LONG iNumColumns = 1; iNumColumns <= 4; iNumColumns++) { // parameter: num color columns: 1, 2, 3, 4
          MenuAddString(COLOURTABLE_M_OPTIONS_SUBM_COLS_TEXT | iNumColumns,
                        LongToString(iNumColumns));
        }
   	  MenuSubEnd();
   		MenuSubBegin(GeLoadString(COLOURTABLE_M_OPTIONS_STR_COLS_NOTEXT));
        for (LONG iNumColumns = 1; iNumColumns <= 8; iNumColumns++) { // parameter: num color columns: 1, 2, 3, 4, 5, 6, 7, 8
          MenuAddString(COLOURTABLE_M_OPTIONS_SUBM_COLS_NOTEXT | iNumColumns,
                        LongToString(iNumColumns << 2)); // parameter scaled down by 4 (to fit in nibble)
        }
   	  MenuSubEnd();
    MenuSubEnd();
 		MenuSubBegin(GeLoadString(COLOURTABLE_M_STR_RESET));
      for (LONG idxGroup = 0; idxGroup < 6 /*TODO*/; idxGroup++) {
        MenuAddString(COLOURTABLE_M_RESET_BASE | idxGroup, GeLoadString(COLOURTABLE_SUBDIALOG_NAME_RAL + idxGroup));
      }
      MenuAddSeparator();
      MenuAddString(COLOURTABLE_M_RESET_CMD_REMOVE, GeLoadString(COLOURTABLE_M_RESET_STR_REMOVE));
    MenuSubEnd();
	MenuFinished();
  MenuInitString(COLOURTABLE_M_OPTIONS_SUBM_LAYOUT | g_iNumColumns, TRUE, TRUE);
  MenuInitString(COLOURTABLE_M_OPTIONS_SUBM_COLS_TEXT | (g_pBcPrefs->GetLong(DLG_RAL + BC_OFFS_L_NUM_COLUMNS_TEXT, 4)),
                 TRUE, TRUE); // TODO number of columns is stored with each group, using RAL here is as good as any other... nevertheless ugly!
  MenuInitString(COLOURTABLE_M_OPTIONS_SUBM_COLS_NOTEXT | (g_pBcPrefs->GetLong(DLG_RAL + BC_OFFS_L_NUM_COLUMNS_NOTEXT, 12) >> 2),
                 TRUE, TRUE); // TODO number of columns is stored with each group, using RAL here is as good as any other... nevertheless ugly!
  return iResult;
}

void ColourTableAsyncDialog::ReLayout(void)
{
  LONG iNumGroups = 0;
  LONG iFlag = 0x00000001;

  LayoutFlushGroup(COLOURTABLE_SCROLLGROUP_CONTENT);  // in scrollgroup
  GroupBegin(COLOURTABLE_SCROLLGROUP_GROUPS, BFH_SCALEFIT | BFV_SCALEFIT, g_iNumColumns, 0, "", BFV_GRIDGROUP_ALLOW_WEIGHTS); // Another group to 
  {
    for (LONG idxGroup = 0; idxGroup < COLOURTABLE_NUM_GROUPS; idxGroup++) {
      if (g_pGadQuickTab->IsSelected(COLOURTABLE_SUBDIALOG_ID_BASE + idxGroup)) {
        g_iFlagsGroup |= iFlag;
        AddSubDialog(COLOURTABLE_SUBDIALOG_BASE + iNumGroups, BFH_SCALEFIT | BFV_SCALEFIT); // TODO error handling
        AttachSubDialog(g_pArrSubdialogs[idxGroup], COLOURTABLE_SUBDIALOG_BASE + iNumGroups);
        LayoutChanged(GadgetPtr(COLOURTABLE_SUBDIALOG_BASE + iNumGroups));
        iNumGroups++;
      } else {
        g_iFlagsGroup &= ~iFlag;
      }
      iFlag <<= 1;
    }
  }
  GroupEnd();
  g_iNumRows = DIV_CEIL(iNumGroups, g_iNumColumns);
  g_iNumColumnsActual = DIV_CEIL(iNumGroups, g_iNumRows);
  g_pBcWeights->SetLong(GROUPWEIGHTS_PERCENT_H_CNT, g_iNumRows); // number of rows - has to be equal to the given layout
  g_pBcWeights->SetLong(GROUPWEIGHTS_PERCENT_W_CNT, g_iNumColumnsActual); // number of columns - has to be equal to the given layout
  GroupWeightsLoad(COLOURTABLE_SCROLLGROUP_GROUPS, *g_pBcWeights);
  LayoutChanged(GadgetPtr(COLOURTABLE_SCROLLGROUP)); // scrollgroup
  return;
}

Bool ColourTableAsyncDialog::InitValues(void)
{
	// first call the parent instance
	if (!SUPER::InitValues()) {
    return FALSE;
  }
	return TRUE;
}

void ColourTableAsyncDialog::CommandOptions(LONG id)
{
  if ((id & COLOURTABLE_M_MASK_SUBM) == COLOURTABLE_M_OPTIONS_SUBM_LAYOUT) {
    // deactivate all in group
    for (LONG idxLayout = 1; idxLayout <= 8; idxLayout *= 2) { // matches loop in CreateLayout()
      MenuInitString(COLOURTABLE_M_OPTIONS_SUBM_LAYOUT | idxLayout, TRUE, FALSE);
    }
    // change dialog layout
    g_iNumColumns = COLOURTABLE_M_OPTIONS_GET_PARAM(id);
    ReLayout();
    if (g_pBcPrefs) {
      g_pBcPrefs->SetLong(BC_L_NUM_COLUMNS, g_iNumColumns);
      g_pBcPrefs->SetLong(BC_L_NUM_COLUMNS_ACTUAL, g_iNumColumnsActual);
      g_pBcPrefs->SetLong(BC_L_NUM_ROWS, g_iNumRows);
    }
  } else if ((id & COLOURTABLE_M_MASK_SUBM) == COLOURTABLE_M_OPTIONS_SUBM_COLS_TEXT) {
    // deactivate all in group
    for (LONG iNumColumns = 1; iNumColumns <= 4; iNumColumns++) { // matches loop in CreateLayout()
      MenuInitString(COLOURTABLE_M_OPTIONS_SUBM_COLS_TEXT | iNumColumns, TRUE, FALSE);
    }
    // set number of columns in all groups
    for (LONG idxGroup = 0; idxGroup < COLOURTABLE_NUM_GROUPS; idxGroup++) {
      g_pArrSubdialogs[idxGroup]->ColumnsSet(1, COLOURTABLE_M_OPTIONS_GET_PARAM(id));
    }
  } else if ((id & COLOURTABLE_M_MASK_SUBM) == COLOURTABLE_M_OPTIONS_SUBM_COLS_NOTEXT) {
    // deactivate all in group
    for (LONG iNumColumns = 1; iNumColumns <= 8; iNumColumns++) { // matches loop in CreateLayout()
      MenuInitString(COLOURTABLE_M_OPTIONS_SUBM_COLS_NOTEXT | iNumColumns, TRUE, FALSE);
    }
    // set number of columns in all groups
    for (LONG idxGroup = 0; idxGroup < COLOURTABLE_NUM_GROUPS; idxGroup++) {
      g_pArrSubdialogs[idxGroup]->ColumnsSet(0, COLOURTABLE_M_OPTIONS_GET_PARAM(id) << 2);
    }
  }
  MenuInitString(id, TRUE, TRUE); // activate this one
  return;
}

Bool ColourTableAsyncDialog::Command(LONG id, const BaseContainer &msg)
{
  if (COLOURTABLE_QUICKTAB == id) {
    ReLayout();
    if (g_pBcPrefs) {
      g_pBcPrefs->SetLong(BC_L_FLAGS_GROUP, g_iFlagsGroup);
    }
    return TRUE;
  }
  if (COLOURTABLE_M_VIEW_BASE == (id & COLOURTABLE_M_MASK)) {
    Bool iFlag = g_pArrSubdialogs[COLOURTABLE_M_VIEW_GET_GROUP(id)]->FlagsDisplayToggle(COLOURTABLE_M_VIEW_GET_FLAG(id));
    MenuInitString(id, TRUE, iFlag);
    return TRUE;
  }
  if (COLOURTABLE_M_OPTIONS_BASE == (id & COLOURTABLE_M_MASK)) {
    CommandOptions(id);
  }
  if (COLOURTABLE_M_RESET_BASE == (id & COLOURTABLE_M_MASK)) {
    if (id == COLOURTABLE_M_RESET_CMD_REMOVE) {
      g_sdUserDoc.RemoveFromDoc();
    } else {
      g_pArrSubdialogs[COLOURTABLE_M_RESET_GET_GROUP(id)]->ReLayout();
    }
    return TRUE;
  }
	return TRUE;
}

Bool ColourTableAsyncDialog::CoreMessage(LONG id,const BaseContainer &msg)
{
  return SUPER::CoreMessage(id, msg);
}

LONG ColourTableAsyncDialog::Message(const BaseContainer &msg,BaseContainer &result)
{
	switch (msg.GetId())
	{
  case BFM_WEIGHTS_CHANGED:
    // if the weights change because of user interaction you will get notified
    if (msg.GetLong(BFM_WEIGHTS_CHANGED) == COLOURTABLE_SCROLLGROUP_GROUPS) {
      GroupWeightsSave(COLOURTABLE_SCROLLGROUP_GROUPS, *g_pBcWeights);
    }
    break;
  case BFM_GETCURSORINFO:
    {
      // pluginprogrammers can return a help id in cursorinfo so that the onlinehelpsystem displays the help for a dialog
      HandleHelpString(msg, result, "PLUGIN_CMD_???"); // TODO
    }
    break;
	}
	return SUPER::Message(msg, result);
}


// CLASS ColourTable
LONG ColourTable::GetState(BaseDocument *doc)
{
	return CMD_ENABLED;
}

Bool ColourTable::Execute(BaseDocument *doc)
{
	return g_dlg.Open(DLG_TYPE_ASYNC, ID_COLOURTABLE, -1, -1, 300, 250);
}

Bool ColourTable::RestoreLayout(void *secret)
{
	return g_dlg.RestoreLayout(ID_COLOURTABLE, 0, secret);
}

Bool RegisterColourTable(void)
{
  return RegisterCommandPlugin(ID_COLOURTABLE,
                               GeLoadString(COLOURTABLE_PLUGIN_NAME),
                               0,
                               AutoBitmap("colourtable.tif"),
                               String("ColourTable Help"), // TODO load str
                               gNew ColourTable);
}
